﻿using System;
using System.Collections.Generic;

namespace UnitBox {
	internal class SwapQueue {

		#region class fields

		private static readonly IDictionary<Type, object> _swapQueues;
		private static readonly IList<Type> _typesToStoreSwappedInstancesFor;
		private static readonly IList<object> _instancesThatWereSwapped;

		#endregion


		#region constructors

		static SwapQueue() {
			_swapQueues = new Dictionary<Type, object>();
			_typesToStoreSwappedInstancesFor = new List<Type>();
			_instancesThatWereSwapped = new List<object>();
		}

		#endregion


		#region public methods

		/// <summary>
		/// Returns whether there are any instances pending to be swapped for the given type.
		/// </summary>
		/// <typeparam name="T">the type for which to check whether there are pending instances to be swapped</typeparam>
		/// <returns></returns>
		public static bool HasPendingInstancesToSwap<T>() {
			return _swapQueues.ContainsKey( typeof(T) );
		}


		/// <summary>
		/// Queues up a swap for the next instance of the given type.
		/// </summary>
		/// <typeparam name="T">the type to perform swap for</typeparam>
		/// <param name="instanceToSwap">the instance with which to swap</param>
		public static void SwapNextInstanceWith<T>( T instanceToSwap ) {
			Type typeToSwap = typeof(T);

			// create a queue of instances to swap if no instances are currently pending
			Queue<T> swapQueueForType;
			if( /* no instance has yet been configured to be swapped for the type */ !_swapQueues.ContainsKey( typeToSwap )) {
				swapQueueForType = new Queue<T>();
				_swapQueues[typeToSwap] = swapQueueForType;
			} else {
				swapQueueForType = (Queue<T>) _swapQueues[typeToSwap];
			}

			// queue up the instance so that it can be swapped upon future creations
			swapQueueForType.Enqueue( instanceToSwap );
		}


		/// <summary>
		/// Retrieves the next instance to swap for the given type.
		/// </summary>
		/// <typeparam name="T">the type which has an pending instance to swap</typeparam>
		/// <returns></returns>
		public static T GetNextPendingInstanceToSwap<T>() {
			Type typeToSwapFor = typeof(T);

			// ensure the queue has a pending instance to swap
			if( !_swapQueues.ContainsKey( typeToSwapFor ))
				throw new InvalidOperationException( string.Format(
					"The type {0} has no pending instances to swap.", typeToSwapFor ));

			// retrieve the next instance
			Queue<T> swapQueue = (Queue<T>) _swapQueues[typeToSwapFor];
			T swappedInstance = swapQueue.Dequeue();

			// store that the instance was swapped if configured to do so
			if( _typesToStoreSwappedInstancesFor.Contains( typeToSwapFor ))
				_instancesThatWereSwapped.Add( swappedInstance );

			// remove queue if there are no more instances to swap for the given type
			if( swapQueue.Count == 0 )
				_swapQueues.Remove( typeToSwapFor );

			return swappedInstance;
		}


		// TODO(design): clear the types to remember swapped instances for
		/// <summary>
		/// Configures the queue to remember all future instances that are going to be swapped
		/// which are of the given type.
		/// </summary>
		/// <typeparam name="T">the type for which to remember future swapped instances</typeparam>
		public static void RememberSwappedInstances<T>() {
			_typesToStoreSwappedInstancesFor.Add( typeof(T) );
		}


		/// <summary>
		/// Returns whether a swap was performed for the given instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="instanceThatShouldHaveBeenSwapped"></param>
		/// <returns></returns>
		public static bool WasSwapped<T>( T instanceThatShouldHaveBeenSwapped ) {
			return _instancesThatWereSwapped.Contains( instanceThatShouldHaveBeenSwapped );
		}


		public static void Reset() {
			_swapQueues.Clear();
			_typesToStoreSwappedInstancesFor.Clear();
			_instancesThatWereSwapped.Clear();
		}

		#endregion

	}
}
